﻿using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using ATMCell;
using ATMConst;
using Package;

namespace ClientEndpoint
    
{
    public class CableCloudEndPoint
    {
		static CableCloudEndPoint rInstance_ = null;

	    public TcpClient tcpConnection { get; private set; }
	    public NetworkStream stream { get; private set; }
	    readonly IPAddress cableCloudAddress_;
	    readonly int registrationPort_;
        int mainPort_;
        ATMCell.AtmCell.AtmHeader headerr;
        public ATMCell.AtmCell.AtmHeader getHeader
        {
           get {return headerr; }
        }

        public int mainPort
        {
            get { return mainPort_; }
             private set { mainPort_ = value; }
        }
        CableCloudEndPoint(IPAddress address, int port, cellPackageHandler handler)
		{
			cableCloudAddress_ = address;
			registrationPort_ = port;
			connectToCableCloud();
			tcpConnection = new TcpClient();
            tcpConnection.NoDelay = true;
			var bytes = new byte[Const.ATM_DATA_SIZE];
			var cell = new AtmCell(bytes,
									new BitArray((int)AtmCell.Size.Hec),
									new BitArray((int)AtmCell.Size.Vpi),
									new BitArray((int)AtmCell.Size.Vci),
									new BitArray((int)AtmCell.Size.Payload),
									new BitArray((int)AtmCell.Size.Priority));
			var cellPackage = new CellPackage(123, cell);
			tcpConnection.SendBufferSize = Package.Package.serializePackage(cellPackage).Length;
            tcpConnection.Connect(address, mainPort_);
			stream = tcpConnection.GetStream();
			receivingHandler_ = handler;
		}
		static public CableCloudEndPoint getInstance(IPAddress address = null, int port = 50001, cellPackageHandler handler=null)
		{
			if (rInstance_ == null)
			{
				rInstance_ = new CableCloudEndPoint(address, port, handler);
			}
			return rInstance_;
		}
		void connectToCableCloud()
		{
			Socket registrationSocket = new Socket(	SocketType.Stream,
													ProtocolType.Tcp);
			registrationSocket.Connect(cableCloudAddress_, registrationPort_);
			registrationSocket.NoDelay = true;
            ClientProperties properties = ClientProperties.getInstance();
			Package.Package helloPackage = new Package.HelloPackage(
                    properties.portNumbers,
                    properties.name,
                    properties.nodeType);
			byte[] serializedPackage = Package.Package.serializePackage(helloPackage);
			byte[] receivedPackage = new byte[ATMConst.Const.BUFFER_SIZE];
			Package.Package package;
			while (true)
			{
				SocketError errorCode;
				registrationSocket.Send(serializedPackage,
										0,
										serializedPackage.Length,
										SocketFlags.None,
										out errorCode);
				//if(error exist
				// do something
				//continue
				Console.WriteLine("Waiting for OK");
				registrationSocket.Receive(	receivedPackage,
											0,
											ATMConst.Const.BUFFER_SIZE,
											SocketFlags.None,
											out errorCode);
				//if(error exist
				// do something
				//continue
				Console.WriteLine("After package");
				
				package = Package.Package.deserializePackage(receivedPackage);
				if(package.code == ATMConst.Const.Code.HELLO_OK)
					break;
				if (package.code == ATMConst.Const.Code.NAME_ERROR)
				{
					throw new WrongNodeNameException(package as Package.NameErrorPackage);
					
				}
				if (package.code == ATMConst.Const.Code.INVALID_MESSAGE)
				{
					throw new WrongCodeException(package as Package.InvalidMessagePackage);
				}
			}
			Package.HelloOkPackage OKPackage = package as Package.HelloOkPackage;
			mainPort_ = (int)OKPackage.data;
            Console.WriteLine("mainPort = {0}", mainPort_);
			registrationSocket.Close();
		}
		public void sendCellToNode(ATMCell.AtmCell cell, int nodePort)
		{
			Package.CellPackage package = new Package.CellPackage(nodePort,
																	cell);
            byte[] bytes = Package.Package.serializePackage(package);
			stream.Write(bytes,
							0,
							bytes.Length);
		}
		/** 
		 * asynchronous function
		 * 
		 */
		ATMCell.AtmCell receiveCell()
		{
            var package = receiveCellPackage();
			if (package != null)
				return package.data as ATMCell.AtmCell;
			else 
				return null;
		}
        Package.Package receiveCellPackage()
        {
            byte[] buffer = new byte[ATMConst.Const.BUFFER_SIZE];
            stream.Read(buffer,
                        0,
                        buffer.Length);
            Package.Package package = Package.Package.deserializePackage(buffer);
            if (package.code == ATMConst.Const.Code.CELL)
                return package;
            else
                return null;
        }
		public void startListeningForCells()
		{
			while (true)
			{
				Console.WriteLine("listening");
				var package = receiveCellPackage();
                var cell = (package.data as ATMCell.AtmCell);
                headerr = cell.header;
				if (cell == null)
				{
					Console.WriteLine("bad cell received");
					continue;
				}
				receivingHandler_(package as Package.CellPackage);
			}
		}
		public delegate void cellPackageHandler(Package.CellPackage package);

	    readonly cellPackageHandler receivingHandler_;
        public static void debugHandler(Package.CellPackage package)
		{
			Console.WriteLine("Odebrano:");
            var cell = package.data as ATMCell.AtmCell;

			foreach (byte b in cell.data)
				Console.Write((char)b);
			Console.WriteLine("=====End=====");
            
		}
    }
}
